#include "gui.h"

#include "timer.h"

#include <log.h>

#define GUI_MAX_CONTEXTS 32
#define GUI_EVENT_STACK_SIZE 256

////////////////////////////////////////////////////////////////////////////////

struct GUIContext
{
  Screen* screen;
  Widget* focus_widget;
  State* state;
};

struct GUIState
{
  Stack<GUIContext, GUI_MAX_CONTEXTS> context_stack;
  Font default_font;
};

static GUIState state;

////////////////////////////////////////////////////////////////////////////////

Widget::Widget()
{
  _parent = NULL;
  _pos.set(0, 0);
  _dim.set(0, 0);
}

void Widget::getGlobalPos(Point* pos) const
{
  Widget* par = _parent;
  
  pos->x = _pos.x;
  pos->y = _pos.y;
  
  while (par)
  {
    pos->x += par->_pos.x;
    pos->y += par->_pos.y;
    
    par = par->_parent;
  }
}

bool Widget::intersect(const Point& pos)
{
  return pos.x >= _pos.x && pos.y >= _pos.y && pos.x < _pos.x + _dim.w && pos.y < _pos.y + _dim.h;
}

bool Widget::handleEvent(const GUIEvent* event)
{
  bool handled = false;
  Point pos;
  
  if (event->getType() == GUI_EVENT_MOUSE_PRESS)
  {
    MousePressEvent press_event = ((GUIMousePressEvent*)event)->getPress();
    
    getGlobalPos(&pos);
    press_event.pos.x -= pos.x;
    press_event.pos.y -= pos.y;
    
    handled = handleMousePress(press_event);
  }
  else if (event->getType() == GUI_EVENT_MOUSE_RELEASE)
  {
    handled = handleMouseRelease(((GUIMousePressEvent*)event)->getPress());
  }
  else if (event->getType() == GUI_EVENT_KEY_DOWN)
  {
    handled = handleKeyDown((GUIKeyDownEvent*)event);
  }
  
  if (!handled)
  {
    if (_parent)
    {
      handled = _parent->handleEvent(event);
    }
  }
  
  return handled;
}

void Widget::postEvent(unsigned type)
{
  GUIWidgetEvent event(this, type);
  
  GUI_postEvent(this, &event);
}

////////////////////////////////////////////////////////////////////////////////

Screen::Screen()
{
  _background = NULL;
  _content = NULL;
}
  
void Screen::setBackground(Widget* widget)
{
  widget->setParent(this);
  
  _background = widget;
}

void Screen::setContent(Widget* widget)
{
  widget->setParent(this);
  
  _content = widget;
}

Widget* Screen::getWidget(const Point& pos)
{
  Point p;
  
  if (_content && _content->intersect(pos))
  {
    p = pos - _content->getPos();
    
    return _content->getWidget(p);
  }
  
  if (_background && _background->intersect(pos))
  {
    p = pos - _background->getPos();
    
    return _background->getWidget(p);
  }
  
  return this;
}

void Screen::doLayout()
{
  if (_background)
  {
    _background->doLayout();
  }
  
  if (_content)
  {
    _content->doLayout();
  }
}

void Screen::draw()
{
  if (_background)
  {
    _background->draw();
  }
  
  if (_content)
  {
    _content->draw();
  }
}

void Screen::onSetDim()
{
  if (_background)
  {
    _background->setDim(_dim);
  }
  
  if (_content)
  {
    _content->setDim(_dim);
  }
}

////////////////////////////////////////////////////////////////////////////////

void State::handleEvent(const GUIEvent* event)
{
  if (event->getType() == GUI_EVENT_KEY_DOWN)
  {
    handleKeyDown((GUIKeyDownEvent*)event);
  }
  else if (event->getType() == GUI_EVENT_WIDGET)
  {
    handleWidgetEvent((GUIWidgetEvent*)event);
  }
  
}

////////////////////////////////////////////////////////////////////////////////

bool GUI_init()
{
  Font_init(&state.default_font);
  
  return true;
}

void GUI_quit()
{
  Font_free(&state.default_font);
}

void GUI_pushContext()
{
  state.context_stack.push(GUIContext());
  
  state.context_stack.top().screen = new Screen();
  state.context_stack.top().focus_widget = NULL;
  state.context_stack.top().state = NULL;
}

void GUI_popContext()
{
  if (state.context_stack.canPop())
  {
    delete state.context_stack.top().screen;
    
    state.context_stack.pop();
  }
}

void GUI_loadDefaultFont(const char* filename, int size)
{
  Font_load(&state.default_font, size, filename);
}

const Font& GUI_getDefaultFont()
{
  return state.default_font;
}

void GUI_print(const Point& pos, Colour colour, const char* text)
{
  Font_print(state.default_font, pos, colour, text);
}

void GUI_print(const Rect& rect, TextHorzAlign horz_align,
  TextVertAlign vert_align, Colour colour, const char* text)
{
  Font_print(state.default_font, rect, horz_align, vert_align, colour, text);
}

void GUI_setBackground(Widget* widget)
{
  state.context_stack.top().screen->setBackground(widget);
}

void GUI_setContent(Widget* widget)
{
  state.context_stack.top().screen->setContent(widget);
}

void GUI_postEvent(Widget* widget, const GUIEvent* event)
{
  if (widget)
  {
    if (widget->handleEvent(event))
    {
      return;
    }
  }
  
  state.context_stack.top().state->handleEvent(event);
}

bool GUI_hasFocus(Widget* widget)
{
  return state.context_stack.top().focus_widget == widget;
}

bool GUI_processEvents()
{
  GUIContext& context = state.context_stack.top();
  Event event;
  
  while (Input_pollEvent(&event))
  {
    if (event.type == EVENT_QUIT)
    {
      return false;
    }
    else if (event.type == EVENT_MOUSE_PRESS)
    {
      GUIMousePressEvent mouse_press_event(GUI_EVENT_MOUSE_PRESS,
        event.mouse_press);
      
      Widget* widget = context.screen->getWidget(event.mouse_press.pos);
      
      if (!widget)
      {
        continue;
      }
      
      if (context.focus_widget)
      {
        if (widget != context.focus_widget)
        {
          context.focus_widget->loseFocus();
          
          widget->gainFocus();
          
          context.focus_widget = widget;
        }
      }
      else
      {
        context.focus_widget = widget;
      }
      
      context.focus_widget->handleEvent(&mouse_press_event);
    }
    else if (event.type == EVENT_MOUSE_RELEASE)
    {
      if (context.focus_widget)
      {
        GUIMousePressEvent mouse_press_event(GUI_EVENT_MOUSE_RELEASE,
          event.mouse_press);
        
        context.focus_widget->handleEvent(&mouse_press_event);
      }
    }
    else if (event.type == EVENT_KEY_DOWN)
    {
      GUIKeyDownEvent key_down_event(event.key_press);
      
      GUI_postEvent(context.focus_widget, &key_down_event);
    }
  }
  
  return true;
}

void GUI_update(unsigned dm, float dt)
{
  state.context_stack.top().state->update(dm, dt);
}

void GUI_draw()
{
  GUIContext& context = state.context_stack.top();
  
  context.screen->doLayout();
  context.screen->setDim(Graphics_getScreenDim());
  
  context.screen->draw();
}

void GUI_runState(State* s)
{
  GUI_pushContext();
  
  s->create();
  
  state.context_stack.top().state = s;
  
  while (!s->isDone())
  {
    Graphics_beginDrawing();
    
    GUI_draw();
    
    Graphics_endDrawing();
    
    Graphics_updateScreen();
    
    GUI_processEvents();
    
    Timer_update();
    
    GUI_update(Timer_getDM(), Timer_getDT());
  }
  
  s->destroy();  
  
  GUI_popContext();
}
